<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>WebGL 动态爱心</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      canvas {
        display: block;
        background: #f0f0f0;
        margin: 50px auto 0;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script>
      // 获取 WebGL 上下文
      const canvas = document.getElementById('canvas')
      const gl = canvas.getContext('webgl')

      if (!gl) {
        console.log('无法获取 WebGL 上下文')
        return
      }

      // 顶点着色器源码
      const vsSource = `
        attribute vec4 aPosition;
        void main() {
          gl_Position = aPosition;
        }
      `

      // 片元着色器源码
      const fsSource = `
        precision mediump float;
        uniform vec2 uResolution;
        uniform float uTime;
        
        void main() {
          vec2 st = gl_FragCoord.xy / uResolution.xy;
          st = st * 2.0 - 1.0;
          st.y *= uResolution.y / uResolution.x;
          
          // 移动爱心
          st.x += 0.5 * sin(uTime);
          
          float x = st.x;
          float y = st.y;
          
          float heart = pow(pow(x, 2.0) + pow(y - pow(abs(x), 2.0/3.0), 2.0), 1.0 / 3.0);
          float color = heart <= 0.6 ? 1.0 : 0.0;
          
          gl_FragColor = vec4(color, 0.0, 0.0, 1.0);
        }
      `

      // 初始化着色器程序
      function initShader(gl, vsSource, fsSource) {
        const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)
        const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)

        const shaderProgram = gl.createProgram()
        gl.attachShader(shaderProgram, vertexShader)
        gl.attachShader(shaderProgram, fragmentShader)
        gl.linkProgram(shaderProgram)

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
          console.error('无法初始化着色器程序: ' + gl.getProgramInfoLog(shaderProgram))
          return null
        }

        return shaderProgram
      }

      // 加载着色器
      function loadShader(gl, type, source) {
        const shader = gl.createShader(type)
        gl.shaderSource(shader, source)
        gl.compileShader(shader)

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
          console.error('着色器编译失败: ' + gl.getShaderInfoLog(shader))
          gl.deleteShader(shader)
          return null
        }

        return shader
      }

      const shaderProgram = initShader(gl, vsSource, fsSource)
      gl.useProgram(shaderProgram)

      // 设置顶点数据
      const vertices = new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0])

      const buffer = gl.createBuffer()
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

      const aPosition = gl.getAttribLocation(shaderProgram, 'aPosition')
      gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
      gl.enableVertexAttribArray(aPosition)

      // 获取 uniform 变量的位置
      const uResolution = gl.getUniformLocation(shaderProgram, 'uResolution')
      const uTime = gl.getUniformLocation(shaderProgram, 'uTime')

      // 设置分辨率 uniform
      gl.uniform2f(uResolution, canvas.width, canvas.height)

      let startTime = null

      // 绘制函数
      function draw(timestamp) {
        if (!startTime) startTime = timestamp
        const elapsed = timestamp - startTime

        // 更新时间 uniform
        gl.uniform1f(uTime, elapsed * 0.001)

        // 清除画布
        gl.clearColor(1.0, 1.0, 1.0, 1.0)
        gl.clear(gl.COLOR_BUFFER_BIT)

        // 绘制
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)

        // 请求下一帧
        requestAnimationFrame(draw)
      }

      // 开始动画
      requestAnimationFrame(draw)
    </script>
  </body>
</html>
